package com.tcoding.client.controller.test.controlsfx;

import com.jfoenix.controls.JFXButton;
import com.tcoding.client.utils.SvgGraphicUtil;
import io.datafx.controller.ViewController;
import io.datafx.controller.flow.Flow;
import io.datafx.controller.flow.FlowException;
import io.datafx.controller.flow.FlowHandler;
import javafx.beans.binding.Bindings;
import javafx.beans.property.*;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.concurrent.Task;
import javafx.fxml.FXML;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.*;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.FontPosture;
import javafx.scene.text.FontWeight;
import javafx.stage.Window;
import javafx.util.Duration;
import javafx.util.StringConverter;
import org.controlsfx.control.*;
import org.controlsfx.control.action.Action;
import org.controlsfx.control.table.TableRowExpanderColumn;
import org.controlsfx.dialog.*;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.Glyph;
import org.controlsfx.tools.Borders;
import org.controlsfx.validation.ValidationSupport;
import org.controlsfx.validation.Validator;

import javax.annotation.PostConstruct;
import java.time.LocalDate;
import java.time.Period;
import java.util.Arrays;
import java.util.List;
import java.util.Random;

/**
 * @author 唐全成
 * @Date: 2022/9/7 11:16
 * @description
 **/
@ViewController(value = "/fxml/controlsfx/controlsdemo.fxml", title = "大数据量测试")
public class ControlsDemoController {

    @FXML
    private StackPane rootPane;
    @FXML
    private JFXButton notificationLeftTop;
    @FXML
    private JFXButton notificationLeftBottom;
    @FXML
    private JFXButton notificationRightTop;
    @FXML
    private JFXButton notificationRightBottom;
    @FXML
    private JFXButton dialog1;
    @FXML
    private JFXButton dialog2;
    @FXML
    private JFXButton dialog3;
    @FXML
    private JFXButton dialog4;
    @FXML
    private JFXButton notify1;
    @FXML
    private JFXButton notify2;
    @FXML
    private JFXButton notify3;
    @FXML
    private JFXButton notify4;

    @FXML
    private StackPane selectListViewSection;
    @FXML
    private StackPane checkComboBoxContainer;
    @FXML
    private StackPane borderContainer;
    @FXML
    private StackPane segmentButtonContainer;
    @FXML
    private StackPane checkedTreeViewContainer;
    @FXML
    private StackPane ratingContainer;
    @FXML
    private StackPane searchableComboboxContainer;
    @FXML
    private StackPane rangerSliderContainer;

    @FXML
    private TabPane tabPane;

    @FXML
    private Tab iconTab;
    @FXML
    private Tab filterTableTab;
    @FXML
    private Tab customFilterTableTab;
    @FXML
    private Tab colorGridTab;
    @FXML
    private SplitPane splitTablePane;

    private NotificationPane notificationPane;
    @PostConstruct
    private void init() {
        notificationLeftTop.setOnAction(e->notification(Pos.TOP_LEFT));
        notificationLeftBottom.setOnAction(e->notification(Pos.BOTTOM_LEFT));
        notificationRightTop.setOnAction(e->notification(Pos.TOP_RIGHT));
        notificationRightBottom.setOnAction(e->notification(Pos.BOTTOM_RIGHT));

        dialog1.setOnAction(e -> {
            List<CommandLinksDialog.CommandLinksButtonType> links = Arrays
                    .asList(new CommandLinksDialog.CommandLinksButtonType(
                                    "Add a network that is in the range of this computer",
                                    "This shows you a list of networks that are currently available and lets you connect to one.", false),
                            new CommandLinksDialog.CommandLinksButtonType(
                                    "Manually create a network profile",
                                    "This creates a new network profile or locates an existing one and saves it on your computer",
                                    true /*default*/),
                            new CommandLinksDialog.CommandLinksButtonType("Create an ad hoc network",
                                    "This creates a temporary network for sharing files or and Internet connection", false));

            CommandLinksDialog dlg = new CommandLinksDialog(links);
            dlg.setTitle("Manually connect to wireless network");
            String optionalMasthead = "Manually connect to wireless network";
            dlg.getDialogPane().setContentText("How do you want to add a network?");
            dlg.show();
        });
        dialog2.setOnAction(e->{
            FontSelectorDialog dlg = new FontSelectorDialog(null);
            dlg.show();
        });
        dialog3.setOnAction(e -> {
            Task<Object> worker = new Task<Object>() {
                @Override
                protected Object call() throws Exception {
                    for (int i = 0; i <= 100; i++) {
                        updateProgress(i, 99);
                        updateMessage("progress: " + i);
                        System.out.println("progress: " + i);
                        Thread.sleep(100);
                    }
                    return null;
                }
            };

            ProgressDialog dlg = new ProgressDialog(worker);
            dlg.show();
            Thread th = new Thread(worker);
            th.setDaemon(true);
            th.start();
        });
        dialog4.setOnAction(e -> showValidatedLinearWizard());

        selectListViewSection.getChildren().add(buildListSelectionView());

        notify1.setOnAction(e-> notifyPane(0));
        notify2.setOnAction(e-> notifyPane(1));
        notify3.setOnAction(e-> notifyPane(2));
        notify4.setOnAction(e-> notifyPane(3));

        iconTab.setContent(buildGraphic());


        CheckComboBox<Person> personCheckComboBox = buildCheckComboBox();
        Label personCheckComboBoxResult = new Label();
        personCheckComboBox.getCheckModel().getCheckedItems().addListener(new ListChangeListener<Person>(){

            /**
             * Called after a change has been made to an ObservableList.
             *
             * @param c an object representing the change that was done
             * @see Change
             */
            @Override
            public void onChanged(Change c) {
                ObservableList<Person> list = c.getList();
                StringBuilder stringBuilder = new StringBuilder();
                for (Person checkedItem : list) {
                    stringBuilder.append(checkedItem.getFullName()).append(",");
                }
                personCheckComboBoxResult.setText(stringBuilder.toString());
            }
        });
        VBox v = new VBox();
        personCheckComboBox.getStyleClass().add("cf-combo-box");
        v.getChildren().add(personCheckComboBox);
        v.getChildren().add(personCheckComboBoxResult);
        checkComboBoxContainer.getChildren().add(v);

        splitTablePane.getItems().add(buildTable1());
        splitTablePane.getItems().add(buildTable2());

        borderContainer.getChildren().add(buildBorderDemo());

        segmentButtonContainer.getChildren().addAll(buildSegmentedButton());

        checkedTreeViewContainer.getChildren().add(buildCheckedTree());

        ratingContainer.getChildren().add(buildRating());

        searchableComboboxContainer.getChildren().add(buildSearchableCombobox());

        rangerSliderContainer.getChildren().add(createHorizontalSlider());
        Flow flow = new Flow(FilterTableController.class);
        FlowHandler handler = flow.createHandler();
        try {
            filterTableTab.setContent(handler.start());
        } catch (FlowException e) {
            e.printStackTrace();
        }
        Flow flow2 = new Flow(FilteredTableView2Controller.class);
        FlowHandler handler2 = flow2.createHandler();
        try {
            customFilterTableTab.setContent(handler2.start());
        } catch (FlowException e) {
            e.printStackTrace();
        }

        Flow flow3 = new Flow(ColorGridController.class);
        FlowHandler handler3 = flow3.createHandler();
        try {
            colorGridTab.setContent(handler3.start());
        } catch (FlowException e) {
            e.printStackTrace();
        }

    }


    private void notifyPane(int flag){
        notificationPane = new NotificationPane();
        rootPane.getChildren().add(notificationPane);
        notificationPane.setText("这里是测试消息");
        if(flag ==0){
            notificationPane.setShowFromTop(true);
            notificationPane.getStyleClass().remove(NotificationPane.STYLE_CLASS_DARK);
            if (notificationPane.isShowing()) {
                notificationPane.hide();
            } else {
                notificationPane.show();
            }
        }
        if(flag ==1){
            notificationPane.setShowFromTop(false);
            if (notificationPane.isShowing()) {
                notificationPane.hide();
            } else {
                notificationPane.show();
            }
        }
        if(flag ==2){
            notificationPane.getStyleClass().add(NotificationPane.STYLE_CLASS_DARK);
            if (notificationPane.isShowing()) {
                notificationPane.hide();
            } else {
                notificationPane.show();
            }
        }
        if(flag ==3){
            notificationPane.setCloseButtonVisible(false);
            notificationPane.setShowFromTop(false);
            if (notificationPane.isShowing()) {
                notificationPane.hide();
            } else {
                notificationPane.show();
            }
        }
    }

    private Node buildRating(){
        VBox v = new VBox();
        Rating rating = new Rating();
        Label label = new Label();
        rating.setMax(8);
        rating.setRating(8.0);
        rating.setPartialRating(true);
        rating.ratingProperty().addListener((observable,o,n)->{
            label.setText(String.valueOf(n.doubleValue()));
        });

        v.getChildren().addAll(rating,label);
        return v;

    }

    Region createHorizontalSlider() {
        final TextField minField = new TextField();
        minField.setPrefColumnCount(5);
        final TextField maxField = new TextField();
        maxField.setPrefColumnCount(5);

        final RangeSlider hSlider = new RangeSlider(0, 100, 10, 90);
        hSlider.setShowTickMarks(true);
        hSlider.setShowTickLabels(true);
        hSlider.setBlockIncrement(10);
        hSlider.setPrefWidth(200);

        minField.setText("" + hSlider.getLowValue());
        maxField.setText("" + hSlider.getHighValue());

        minField.setEditable(false);
        minField.setPromptText("Min");

        maxField.setEditable(false);
        maxField.setPromptText("Max");

        minField.textProperty().bind(hSlider.lowValueProperty().asString("%.2f"));
        maxField.textProperty().bind(hSlider.highValueProperty().asString("%.2f"));

        HBox box = new HBox(10);
        box.getChildren().addAll(minField, hSlider, maxField);
        box.setPadding(new Insets(20,0,0,20));
        box.setFillHeight(false);

        return box;
    }

    private Node buildSearchableCombobox(){
        ObservableList<String> stringList = FXCollections.observableArrayList("1111", "2222", "Aaaaa", "Abbbb", "Abccc", "Abcdd", "Abcde", "Bbbb", "bbbb", "Cccc", "Dddd", "Eeee", "Ffff", "gggg", "hhhh", "3333");
        VBox v=new VBox();
        ComboBox<String> searchableStringBox = new SearchableComboBox<>();
        searchableStringBox.setItems(stringList);
        searchableStringBox.setMaxWidth(Double.MAX_VALUE);
        searchableStringBox.setPrefHeight(20.0);
        searchableStringBox.getStyleClass().add("cf-combo-box");
        v.getChildren().add(searchableStringBox);
        return v;
    }


    private Node buildCheckedTree(){

        HBox hBox=new HBox();

        CheckBoxTreeItem<String> treeItem_Jonathan = new CheckBoxTreeItem<>("Jonathan");
        CheckBoxTreeItem<String> treeItem_Eugene = new CheckBoxTreeItem<>("Eugene");
        CheckBoxTreeItem<String> treeItem_Henry = new CheckBoxTreeItem<>("Henry");
        CheckBoxTreeItem<String> treeItem_Samir = new CheckBoxTreeItem<>("Samir");
        Label selectedItemsLabel = new Label();
        Label checkedItemsLabel = new Label();

        CheckBoxTreeItem<String> root = new CheckBoxTreeItem<String>("Root");
        root.setExpanded(true);
        root.getChildren().addAll(
                treeItem_Jonathan,
                treeItem_Eugene,
                treeItem_Henry,
                treeItem_Samir);

        // lets check Eugene to make sure that it shows up in the tree
        treeItem_Eugene.setSelected(true);

        // CheckListView
        CheckTreeView<String> checkTreeView = new CheckTreeView<>(root);
        checkTreeView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
        checkTreeView.getSelectionModel().getSelectedItems().addListener(new ListChangeListener<TreeItem<String>>() {
            @Override public void onChanged(Change<? extends TreeItem<String>> c) {
                updateText(selectedItemsLabel, c.getList());
            }
        });

        checkTreeView.getCheckModel().getCheckedItems().addListener(new ListChangeListener<TreeItem<String>>() {
            @Override public void onChanged(Change<? extends TreeItem<String>> change) {
                updateText(checkedItemsLabel, change.getList());

                while (change.next()) {
                    System.out.println("============================================");
                    System.out.println("Change: " + change);
                    System.out.println("Added sublist " + change.getAddedSubList());
                    System.out.println("Removed sublist " + change.getRemoved());
                    System.out.println("List " + change.getList());
                    System.out.println("Added " + change.wasAdded() + " Permutated " + change.wasPermutated() + " Removed " + change.wasRemoved() + " Replaced "
                            + change.wasReplaced() + " Updated " + change.wasUpdated());
                    System.out.println("============================================");
                }
            }
        });

        hBox.getChildren().addAll(checkTreeView,selectedItemsLabel,checkedItemsLabel);
        return hBox;
    }
    protected void updateText(Label label, ObservableList<? extends TreeItem<String>> list) {
        final StringBuilder sb = new StringBuilder();

        if (list != null) {
            for (int i = 0, max = list.size(); i < max; i++) {
                sb.append(list.get(i).getValue());
                if (i < max - 1) {
                    sb.append(", ");
                }
            }
        }

        final String str = sb.toString();
        label.setText(str.isEmpty() ? "<empty>" : str);
    }
    private Node buildGraphic(){
        GridPane fontDemo = new GridPane();
        fontDemo.setHgap(5);
        fontDemo.setVgap(5);
        int maxColumns = 12;
        int col = 0;
        int row = 0;
        for ( FontAwesome.Glyph glyph:  FontAwesome.Glyph.values() ){
            Color randomColor = new Color( Math.random(), Math.random(), Math.random(), 1);
            Glyph graphic = Glyph.create( "FontAwesome|" + glyph.name()).sizeFactor(2).color(randomColor).useGradientEffect();
            Button button = new Button(glyph.name(), graphic);
            button.setContentDisplay(ContentDisplay.TOP);
            button.setMaxWidth(Double.MAX_VALUE);
            col = col % maxColumns + 1;
            if ( col == 1 ) {
                row++;

            }
            fontDemo.add( button, col, row);
            GridPane.setFillHeight(button, true);
            GridPane.setFillWidth(button, true);
        }

        ScrollPane scroller = new ScrollPane(fontDemo);
        scroller.setFitToWidth(true);
        return scroller;
    }

    private void notification(Pos pos){
        Notifications notificationBuilder = Notifications.create()
                .title( "通知")
                .text("这里是测试消息")
                .graphic(SvgGraphicUtil.buildSvgGraphic("file-empty", 12, Color.GRAY))
                .hideAfter(Duration.seconds(3))
                .position(pos)
                .darkStyle()
                .onAction(e -> System.out.println("Notification clicked on!"))
                .threshold(4,
                        Notifications.create().title("Threshold Notification"));

        notificationBuilder.owner(rootPane);
        notificationBuilder.showError();
    }
    private void showValidatedLinearWizard() {
        Window owner =  null;
        Wizard wizard = new Wizard(owner);
        wizard.setTitle("Validated Linear Wizard");

        // Page 1
        WizardPane page1 = new WizardPane() {
            ValidationSupport vs = new ValidationSupport();
            {
                vs.initInitialDecoration();

                int row = 0;

                GridPane page1Grid = new GridPane();
                page1Grid.setVgap(10);
                page1Grid.setHgap(10);

                page1Grid.add(new Label("Username:"), 0, row);
                TextField txUsername = createTextField("username");
                vs.registerValidator(txUsername, Validator.createEmptyValidator("EMPTY!"));
                page1Grid.add(txUsername, 1, row++);

                page1Grid.add(new Label("Full Name:"), 0, row);
                TextField txFullName = createTextField("fullName");
                page1Grid.add(txFullName, 1, row);

                setContent(page1Grid);
            }

            @Override
            public void onEnteringPage(Wizard wizard) {
                wizard.invalidProperty().unbind();
                wizard.invalidProperty().bind(vs.invalidProperty());
            }
        };

        // Page 2

        WizardPane page2 = new WizardPane() {
            ValidationSupport vs = new ValidationSupport();
            {
                vs.initInitialDecoration();

                int row = 0;

                GridPane page2Grid = new GridPane();
                page2Grid.setVgap(10);
                page2Grid.setHgap(10);

                page2Grid.add(new Label("ControlsFX is:"), 0, row);
                ComboBox<String> cbControlsFX = createComboBox("controlsfx");
                cbControlsFX.setItems(FXCollections.observableArrayList("Cool", "Great"));
                vs.registerValidator(cbControlsFX, Validator.createEmptyValidator("EMPTY!"));
                page2Grid.add(cbControlsFX, 1, row++);

                page2Grid.add(new Label("Where have you heard of it?:"), 0, row);
                TextField txWhere = createTextField("where");
                vs.registerValidator(txWhere, Validator.createEmptyValidator("EMPTY!"));
                page2Grid.add(txWhere, 1, row++);

                page2Grid.add(new Label("Free text:"), 0, row);
                TextField txFreeText = createTextField("freetext");
                page2Grid.add(txFreeText, 1, row);

                setContent(page2Grid);
            }

            @Override
            public void onEnteringPage(Wizard wizard) {
                wizard.invalidProperty().unbind();
                wizard.invalidProperty().bind(vs.invalidProperty());
            }
        };

        // create wizard
        wizard.setFlow(new Wizard.LinearFlow(page1, page2));

        // show wizard and wait for response
        wizard.showAndWait().ifPresent(result -> {
            if (result == ButtonType.FINISH) {
                System.out.println("Wizard finished, settings: " + wizard.getSettings());
            }
        });
    }

    private ListSelectionView<String> buildListSelectionView(){
        ListSelectionView<String> view = new ListSelectionView<>();
        view.getSourceItems()
                .addAll("Katja", "Dirk", "Philip", "Jule", "Armin");
        view.setCellFactory(listView -> {
            ListCell<String> cell = new ListCell<String>() {
                @Override
                public void updateItem(String item, boolean empty) {
                    super.updateItem(item, empty);

                    if (empty) {
                        setText(null);
                        setGraphic(null);
                    } else {
                        setText(item == null ? "null" : item);
                        setGraphic(null);
                    }
                }
            };
            cell.setFont(Font.font("Arial", FontWeight.BOLD,
                    FontPosture.REGULAR, 12));

            return cell;
        });
        view.setSourceHeader(new Label("待选择的"));
        view.setTargetHeader(new Label("已选择的"));
        view.setSourceFooter(new Label("未选择"));
        view.setTargetFooter(new Label("已选择"));

        ObservableList<Action> actions = view.getActions();
        for (Action action : actions) {
            action.getStyleClass().add("cf-but");
        }

        return view;
    }

    private CheckComboBox<Person> buildCheckComboBox(){
        CheckComboBox<Person> checkComboBox2 = new CheckComboBox<>(Person.createDemoList());
        checkComboBox2.setConverter(new StringConverter<Person>() {
            @Override
            public String toString(Person object) {
                return object.getFullName();
            }
            @Override
            public Person fromString(String string) {
                return null;
            }
        });
        checkComboBox2.focusedProperty().addListener((o, ov, nv) -> {
            if(nv) {checkComboBox2.show();} else {checkComboBox2.hide();}
        });

        return checkComboBox2;
    }

    private ObservableList<TableView2Sample.Person2> generateData(int numberOfPeople) {
        ObservableList<TableView2Sample.Person2> persons = FXCollections.observableArrayList();

        for (int i = 0; i < numberOfPeople; i++) {
            final LocalDate date = LocalDate.of(1910 + new Random().nextInt(100), 1+i%11, 1+i%29);
            persons.add(new TableView2Sample.Person2("First Name:  " + i%20,
                    "Last Name: " + i%10,
                    Period.between(date, LocalDate.now()).getYears(),
                    "City: " + i%3, i%10 != 0, date));
        }

        return persons;
    }

    private Node buildBorderDemo(){
        Button button = new Button("Hello World!");
        return Borders.wrap(button)
                .lineBorder()
                .title("Line")
                .color(Color.GREEN)
                .thickness(1)
                .radius(0, 5, 5, 0)
                .build()
                .build();
    }

    private Node buildSegmentedButton(){
        VBox v = new VBox();
        ToggleButton modena_b1 = new ToggleButton("day");
        ToggleButton modena_b2 = new ToggleButton("week");
        ToggleButton modena_b3 = new ToggleButton("month");
        ToggleButton modena_b4 = new ToggleButton("year");
        SegmentedButton segmentedButton_modena = new SegmentedButton(modena_b1, modena_b2, modena_b3, modena_b4);
        segmentedButton_modena.getStyleClass().add(SegmentedButton.STYLE_CLASS_DARK);
        v.getChildren().add(segmentedButton_modena);
        Label label = new Label();
        segmentedButton_modena.getToggleGroup().selectedToggleProperty().addListener((observable,o,n)->{
            label.setText(((ToggleButton) n).getText());
        });
        v.getChildren().add(label);
        return v;
    }

    private TableView2Sample buildTable2(){
        return new TableView2Sample(generateData(20));
    }

    private TableView<Customer> buildTable1(){


        TableView<Customer> tableView = new TableView<>();
        TableRowExpanderColumn<Customer> expanderColumn = new TableRowExpanderColumn<>(this::createEditor);

        TableColumn<Customer, Integer> idColumn = new TableColumn<>("ID");
        idColumn.setCellValueFactory(new PropertyValueFactory<>("id"));

        TableColumn<Customer, String> nameColumn = new TableColumn<>("Name");
        nameColumn.setCellValueFactory(new PropertyValueFactory<>("name"));

        TableColumn<Customer, String> emailColumn = new TableColumn<>("Email");
        emailColumn.setCellValueFactory(new PropertyValueFactory<>("email"));

        tableView.getColumns().addAll(expanderColumn, idColumn, nameColumn, emailColumn);

        tableView.setItems(getCustomers());
        return tableView;
    }

    private ObservableList<Customer> getCustomers() {
        return FXCollections.observableArrayList(
                new Customer(1, "Samantha Stuart", "samantha.stuart@contoso.com"),
                new Customer(2, "Tom Marks", "tom.marks@contoso.com"),
                new Customer(3, "Stuart Gills", "stuart.gills@contoso.com"),
                new Customer(4, "Nicole Williams", "nicole.williams@contoso.com")
        );
    }

    private GridPane createEditor(TableRowExpanderColumn.TableRowDataFeatures<Customer> param) {
        GridPane editor = new GridPane();
        editor.setPadding(new Insets(10));
        editor.setHgap(10);
        editor.setVgap(5);

        Customer customer = param.getValue();

        TextField nameField = new TextField(customer.getName());
        TextField emailField = new TextField(customer.getEmail());

        editor.addRow(0, new Label("Name"), nameField);
        editor.addRow(1, new Label("Email"), emailField);

        Button saveButton = new Button("Save");
        saveButton.setOnAction(event -> {
            customer.setName(nameField.getText());
            customer.setEmail(emailField.getText());
            param.toggleExpanded();
        });

        Button cancelButton = new Button("Cancel");
        cancelButton.setOnAction(event -> param.toggleExpanded());

        editor.addRow(2, saveButton, cancelButton);

        return editor;
    }

    private TextField createTextField(String id) {
        TextField textField = new TextField();
        textField.setId(id);
        GridPane.setHgrow(textField, Priority.ALWAYS);
        return textField;
    }

    private ComboBox<String> createComboBox(String id) {
        ComboBox<String> comboBox = new ComboBox<>();
        comboBox.setId(id);
        GridPane.setHgrow(comboBox, Priority.ALWAYS);
        return comboBox;
    }
    public static class Customer {
        public SimpleIntegerProperty idProperty = new SimpleIntegerProperty(this, "id");
        public SimpleStringProperty nameProperty = new SimpleStringProperty(this, "name");
        public SimpleStringProperty emailProperty = new SimpleStringProperty(this, "email");

        public Customer(Integer id, String name, String email) {
            setId(id);
            setName(name);
            setEmail(email);
        }

        public Integer getId() {
            return idProperty.get();
        }

        public SimpleIntegerProperty idProperty() {
            return idProperty;
        }

        public void setId(int id) {
            this.idProperty.set(id);
        }

        public String getName() {
            return nameProperty.get();
        }

        public SimpleStringProperty nameProperty() {
            return nameProperty;
        }

        public void setName(String name) {
            this.nameProperty.set(name);
        }

        public String getEmail() {
            return emailProperty.get();
        }

        public SimpleStringProperty emailProperty() {
            return emailProperty;
        }

        public void setEmail(String email) {
            this.emailProperty.set(email);
        }

        @Override
        public String toString() {
            return getName();
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {return true;}
            if (o == null || getClass() != o.getClass()) {return false;}

            Customer customer = (Customer) o;

            return getId() != null ? getId().equals(customer.getId()) : customer.getId() == null;
        }

        @Override
        public int hashCode() {
            return getId() != null ? getId().hashCode() : 0;
        }
    }
}

class Person {
    private StringProperty firstName = new SimpleStringProperty();
    private StringProperty lastName = new SimpleStringProperty();
    private ReadOnlyStringWrapper fullName = new ReadOnlyStringWrapper();

    public Person(String firstName, String lastName) {
        this.firstName.set(firstName);
        this.lastName.set(lastName);
        fullName.bind(Bindings.concat(firstName, " ", lastName));
    }

    public static final ObservableList<Person> createDemoList() {
        final ObservableList<Person> result = FXCollections.observableArrayList();
        result.add(new Person("Paul", "McCartney"));
        result.add(new Person("Andrew Lloyd", "Webber"));
        result.add(new Person("Herb", "Alpert"));
        result.add(new Person("Emilio", "Estefan"));
        result.add(new Person("Bernie", "Taupin"));
        result.add(new Person("Elton", "John"));
        result.add(new Person("Mick", "Jagger"));
        result.add(new Person("Keith", "Richerds"));
        return result;
    }

    public final StringProperty firstNameProperty() {
        return this.firstName;
    }

    public final String getFirstName() {
        return this.firstNameProperty().get();
    }

    public final void setFirstName(final String firstName) {
        this.firstNameProperty().set(firstName);
    }

    public final StringProperty lastNameProperty() {
        return this.lastName;
    }

    public final String getLastName() {
        return this.lastNameProperty().get();
    }

    public final void setLastName(final String lastName) {
        this.lastNameProperty().set(lastName);
    }

    public final ReadOnlyStringProperty fullNameProperty() {
        return this.fullName.getReadOnlyProperty();
    }

    public final String getFullName() {
        return this.fullNameProperty().get();
    }
}
